package nz.ac.massey.groupproject.ecs2;

import java.util.ArrayList;
import java.util.HashSet;


public class ECSQuery implements ECSQueryInterface {

	private final ECSWorld            world;
	private final ECSQueryTemplate    queryTempl;

	private final HashSet<ECSEntity>  matches         = new HashSet<>();





	public ECSQuery(ECSWorld world_, ECSQueryTemplate templ) {
		world = world_;
		queryTempl = templ;

		startSubscribing();
		initializeCache();
	}

	public ECSQuery(ECSWorld world_, Class...         compTypes) {  this(world_, ECSQueryTemplate.get(compTypes));  }
	public ECSQuery(ECSWorld world_, ArrayList<Class> compTypes) {  this(world_, ECSQueryTemplate.get(compTypes));  }


	public final ArrayList<ECSEntity> listMatches() {
		var result = new ArrayList<ECSEntity>();

		for (var ent : matches)
			result.add(ent);

		// check that cached Query and uncached Query returns the same matches
		assert (  new HashSet<>(result)  ).equals(  new HashSet<>(new ECSUncachedQuery(world, queryTempl).listMatches())  );

		return result;
	}








	enum EventType {
		ADD_ENTITY, REMOVE_ENTITY,
		ADD_COMPONENT, REMOVE_COMPONENT // TODO CHANGE_COMPONENT
	}

	void onEvent(EventType event, ECSEntity ent, ECSComponent comp) {
		switch (event) {
			case REMOVE_ENTITY:
				matches.remove(ent);
				break;
			case ADD_ENTITY:
				if (queryTempl.matchEntity(ent))  matches.add(ent);
				break;
			case ADD_COMPONENT:
			case REMOVE_COMPONENT:
				if (queryTempl.matchEntity(ent))
					matches.add(ent);
				else
					matches.remove(ent);
				break;
			default:
				assert false : "Internal error: ECSQuery.onEvent() doesn't handle all types of events";
		}
	}

	private void startSubscribing() {
		for (var cls : queryTempl.requiredCompTs) {
			world.subscribers.putIfAbsent(cls, new ArrayList<>());
			var queries = world.subscribers.get(cls);
			queries.add(this);
		}
	}

	private void initializeCache() {
		// FIXME code duplication matches = new ECSUncachedQuery(world_, templ).listMatches();
		for (var ent : world.entities)
			if (queryTempl.matchEntity(ent))
				matches.add(ent);
	}
}
